package com.trifork.scala.script

import tools.nsc.interpreter.Results.Success
import java.lang.String
import runtime.BoxedUnit
import java.io.{BufferedReader, Reader, PrintWriter}
import scala.collection.JavaConversions._
import tools.nsc.Settings
import javax.script._
import tools.nsc.interpreter.IMain


class ScalaScriptEngine(scriptEngineFactory:ScalaScriptEngineFactory) extends ScriptEngine {

  var scriptContext:ScriptContext = scriptEngineFactory.newScriptContext()

  val settings = new Settings()
  settings.usejavacp.value = true

  def combineWithGlobalBindings(bindings:Bindings,context:ScriptContext) = bindings.entrySet().toList ::: scriptContext.getBindings(ScriptContext.GLOBAL_SCOPE).entrySet().toList ::: context.getBindings(ScriptContext.ENGINE_SCOPE).entrySet().toList

  def applyBindings(interpreter:IMain,context:ScriptContext,bindings:Bindings) {
    combineWithGlobalBindings(bindings,context).foreach((e) => interpreter.bind(e.getKey, e.getValue))
  }

  def runScript(script: String, context: ScriptContext, optionalBindings: Option[Bindings]): AnyRef = {
    val writer = new java.io.StringWriter()
    val interpreter = new IMain(settings, new PrintWriter(writer))
    val valueHolder = new ValueHolder

    def resolveReturnValue():AnyRef = {
      val lastVar = interpreter.mostRecentVar
      if ("".equals(lastVar)) null else retreiveMostRecentVar()
    }

    def retreiveMostRecentVar(): AnyRef =
      interpreter.interpret("valueHolder.value = " + interpreter.mostRecentVar) match {
        case Success =>
          valueHolder.value.asInstanceOf[AnyRef] match {
            case empty:BoxedUnit => null
            case other => other
          }
        case other => throw new ScriptException("Error evaluating return value: " + writer)
      }

    applyBindings(interpreter, context, optionalBindings match { case Some(b) => b case None => scriptContext.getBindings(ScriptContext.ENGINE_SCOPE) })
    interpreter.bind("valueHolder", valueHolder)

    val result = interpreter.interpret(script)
    result match {
      case Success => resolveReturnValue()
      case _ => throw new ScriptException(result + ": " + writer)
    }
  }

  def readerToString(reader: Reader): String = {
    def inner(reader: BufferedReader, sb: StringBuilder): String = {
      val line = reader.readLine()
      if (line != null) {
        try {
          inner(reader, sb.append(line + "\n"))
        } finally {
          reader.close()
        }
      }
      sb.toString()
    }
    inner(new BufferedReader(reader), new StringBuilder())
  }

  def runScript(script: String): AnyRef = runScript(script, scriptContext, None)

  def eval(script: String, context: ScriptContext) = runScript(script, context, None)

  def eval(reader: Reader, context: ScriptContext) = runScript(readerToString(reader), context, None)

  def eval(script: String) = runScript(script)

  def eval(reader: Reader) = runScript(readerToString(reader))

  def eval(script: String, bindings: Bindings) = runScript(script, scriptContext, Some(bindings))

  def eval(reader: Reader, bindings: Bindings) = runScript(readerToString(reader), scriptContext, Some(bindings))

  def put(key: String, value: AnyRef) { scriptContext.getBindings(ScriptContext.ENGINE_SCOPE).put(key, value) }

  def get(key: String) = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE).get(key)

  def getBindings(scope: Int) = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE)

  def setBindings(bindings: Bindings, scope: Int) {
    scriptContext.setBindings(bindings, scope)
    if (scope == ScriptContext.GLOBAL_SCOPE) {
      scriptEngineFactory.setGlobalBindings(bindings)
    }
  }

  def createBindings() = new SimpleBindings()

  def getContext = scriptContext

  def setContext(context: ScriptContext) { scriptContext = context }

  def getFactory = new ScalaScriptEngineFactory
}